iT邦幫忙

2025 iThome 鐵人賽

DAY 7
0

邊緣偵測

我們已經知道怎麼找出一張影像的特徵點,但是線的特徵也同樣重要,因此我們要從中提取有意義的線條。

邊緣偵測 (edge detection) 的目標是識別出影像中亮度(或顏色)發生劇烈變化的像素點。這些點連起來,通常就構成了物體的邊界和輪廓。它是許多高階視覺任務的基礎,例如物件分割、形狀分析和物體辨識。

Canny 邊緣偵測

Canny 邊緣偵測是一個相當良好的邊緣偵測演算法,有著三個核心標準:低錯誤率、精確的邊緣定位、對單一邊緣只有單一響應。

這個演算法能分為五個步驟。

  1. 高斯濾波:首先對影像進行高斯濾波,以平滑影像並去除無關的雜訊。

  2. 使用 Sobel 算子計算影像中每個像素點的梯度強度和方向。梯度強度大的地方,很可能就是邊緣。

  3. 非極大值抑制:Sobel 算子產生的邊緣通常很粗。此步驟的目的是將粗邊緣「細化」成單像素寬度的線條。它會檢查一個像素在它的梯度方向上,是否是其鄰域中的局部最大值。如果不是,就將該點設為0。

  4. 雙閾值檢測:使用高與低閾值

  • 梯度強度大於高閾值時,標記為強邊緣點。

  • 梯度強度介於兩個閾值之間,標記為弱邊緣點。

  • 梯度強度小於低閾值時,直接捨棄。

  1. 邊緣跟蹤 :最後,演算法會遍歷所有像素。一個弱邊緣點只有在它與一個強邊緣點相連的情況下,才會被保留下來,否則將被拋棄。

Hough 變換

Canny 演算法給了我們一堆零散的邊緣像素點。但如果我們想知道「這張圖裡有幾條直線?」或「最長的那條線在哪裡?」,Canny 本身無法回答。而這時就需要用到 Hough 變換。

Hough 變換是一種特徵提取技術,用於在影像中辨識特定形狀的實例,最常用於偵測直線和圓形。

其偵測直線的核心思想是:將影像空間中的一個點,變換到參數空間中的一條曲線。

在笛卡兒座標系中,一條直線是

https://ithelp.ithome.com.tw/upload/images/20250817/20178100ofeN3YZ8Lk.png

但這種表示法無法處理垂直線(斜率 m 為無窮大)。

因此,Hough 變換使用極座標表示法

https://ithelp.ithome.com.tw/upload/images/20250817/20178100SRbQTNk4J4.png

其中 ρ 是原點到直線的垂直距離,θ 是該垂直線與x軸的夾角。

此時影像空間中共線的所有點 (x, y),在 (ρ, θ) 參數空間中,它們對應的所有正弦曲線都會交於同一個點。

演算法流程如下:

對輸入影像(通常是 Canny 的輸出)中的每一個邊緣點,計算出它在 Hough 空間中對應的所有 (ρ, θ) 組合,並在一個二維的「累加器」矩陣上投票。

投票結束後,累加器中值最高的點,就對應著原始影像中最顯著的直線。

實作 Canny 演算法與 Hough 變換

import cv2
import numpy as np

# 建議使用有清晰輪廓的圖片
image_path = 'bird.jpg'
image = cv2.imread(image_path)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

# 使用高斯模糊降噪
blurred = cv2.GaussianBlur(gray, (5, 5), 0)

# 使用 Canny 進行邊緣偵測
# cv2.Canny(image, threshold1, threshold2)
# threshold1: 低閾值
# threshold2: 高閾值
canny_edges = cv2.Canny(blurred, 50, 150)

cv2.imshow("Original", image)
cv2.imshow("Canny Edges", canny_edges)
cv2.imwrite("Canny.jpg", canny_edges)
cv2.waitKey(0)
cv2.destroyAllWindows()


# 使用機率霍夫變換來偵測直線
# cv2.HoughLinesP(image, rho_accuracy, theta_accuracy, threshold, minLineLength, maxLineGap)
# threshold: 累加器閾值,得票數高於此值的線才會被回傳
# minLineLength: 線段的最短長度
# maxLineGap: 同一條線上,兩點之間可接受的最大間隙
lines = cv2.HoughLinesP(canny_edges, 1, np.pi / 180, threshold=60, minLineLength=50, maxLineGap=10)

# 建立一個副本來繪製線條
line_image = image.copy()

if lines is not None:
    for line in lines:
        x1, y1, x2, y2 = line[0]
        cv2.line(line_image, (x1, y1), (x2, y2), (0, 255, 0), 2)

cv2.imshow("Hough Lines", line_image)
cv2.imwrite("Hough.jpg", line_image)
cv2.waitKey(0)
cv2.destroyAllWindows()

https://ithelp.ithome.com.tw/upload/images/20250817/20178100qvvzwxYbnp.jpghttps://ithelp.ithome.com.tw/upload/images/20250817/20178100zEkXlBUXEw.jpghttps://ithelp.ithome.com.tw/upload/images/20250817/20178100QfhbHhnjO7.jpg


上一篇
Day 6 - 特徵工程(二) SIFT、SURF 與 ORB
下一篇
Day 8 - 從匹配到變換
系列文
從0開始:傳統圖像處理到深度學習模型23
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言